using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Workflows.Components.DALC;

namespace Workflows.Components
{
    /// <summary></summary>
    public delegate void WorkflowTriggerFired(
        Run run, WorkflowTriggerInstance triggerInstance, List<WorkflowComponentBase> wfComponents, List<Connector> wfConnectors);

    /// <summary></summary>
    public class WorkflowEngine
    {
        #region events 
        public event WorkflowTriggerFired OnWorkflowTriggerFired;
        #endregion
        
        private Dictionary<string, Type> _WfComponentTypesByName;

        /// <summary></summary>
        public WorkflowEngine()
        {
            this._WfComponentTypesByName=new Dictionary<string, Type>();
            string dllFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            DirectoryInfo binDirInfo=new DirectoryInfo(dllFolderPath);
            FileInfo[] dllInfos = binDirInfo.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
            foreach(FileInfo dllFile in dllInfos)
            {
                if (dllFile.Name.ToLower().StartsWith("workflow"))
                {
                    try
                    {
                        Assembly libAssembly = Assembly.LoadFile(dllFile.FullName);
                        Type[] allLibTypes = libAssembly.GetTypes();
                        foreach (Type type in allLibTypes)
                        {
                            if (type.IsSubclassOf(typeof (WorkflowComponentBase)))
                            {
                                this._WfComponentTypesByName.Add(type.FullName, type);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Unable to load assembly: " + ex.ToString());
                    }
                }
            }
        }

        #region process design xml
        /// <summary></summary>
        public WorkflowInstance CreateNewWorkflowInstance(WorkflowDesign wfDesign)
        {
            WorkflowInstance wfInstance=new WorkflowInstance();
            wfInstance.WorkflowID = wfDesign.WorkflowGuid;
            WorkflowTriggerBase triggerComponent = this.GetWorkflowTrigger(wfDesign);
            WorkflowTriggerInstance triggerInstance = WorkflowTriggerInstanceDALC.GetLastWorkflowTriggerInstanceByTriggerComponentID(triggerComponent.ComponentID);
            wfInstance.IsActive = triggerInstance.IsActive;
            wfInstance.AuthorEmailAddress = wfDesign.FailureNotifyEmailAddress;
            wfInstance.WorkflowTriggerID = triggerComponent.ComponentID;
            wfInstance.HasError = false;
            
            return wfInstance;
        }

        /// <summary></summary>
        public bool CanWorkflowStart(WorkflowDesign wfDesign)
        {
            WorkflowTriggerBase triggerComponent = this.GetWorkflowTrigger(wfDesign);
            if(triggerComponent==null)
                return false;

            WorkflowTriggerInstance triggerInstance = WorkflowTriggerInstanceDALC.GetLastWorkflowTriggerInstanceByTriggerComponentID(triggerComponent.ComponentID);
            if (triggerComponent.IsActive)
            {
                if(triggerInstance.LastRunTime>TimeUtil.DbMinTime)
                {
                    if(triggerInstance.NextRunTime<DateTime.Now)
                        return true;
                    else
                        return false;
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return false;
            }
        }
        #endregion

        #region process workflow instance
        /// <summary>
        /// return a flag indicating whether the workflow instance 
        /// will fire a trigger for wf service to handle
        /// </summary>
        /// <returns></returns>
        public bool ProcessWorkflowTrigger(WorkflowDesign wfDesign)
        {
            WorkflowTriggerBase trigger = this.GetWorkflowTrigger(wfDesign);
            WorkflowTriggerInstance lastTriggerInstance = WorkflowTriggerInstanceDALC.GetLastWorkflowTriggerInstanceByTriggerComponentID(trigger.ComponentID);
            ResultBase[] triggerOutputs = trigger.ProcessTrigger(lastTriggerInstance.LastRunTime);
            
            if (triggerOutputs != null && triggerOutputs.Length > 0)
            {
                lastTriggerInstance.WorkflowDesignID = wfDesign.WorkflowGuid;
                lastTriggerInstance.TriggerComponentID = trigger.ComponentID;
                lastTriggerInstance.Interval = trigger.Interval;
                lastTriggerInstance.TimeSpanUnit = trigger.TimeSpanUnit;
                lastTriggerInstance.TriggerType = trigger.TriggerType;


                WorkflowTriggerInstance thisTriggerInstance = lastTriggerInstance;
                if (thisTriggerInstance.FirstStartTime <= TimeUtil.DbMinTime)
                {
                    // thisTriggerInstance.FirstStartTime = TimeUtil.DbMinTime;
                    thisTriggerInstance.FirstStartTime = DateTime.Now;
                }
                thisTriggerInstance.WorkflowTriggerInstanceID = Guid.NewGuid();
                thisTriggerInstance.LastRunTime = DateTime.Now;

                if (lastTriggerInstance.NextRunTime.Add(trigger.CreateTimeSpan()) > DateTime.Now)
                {
                    thisTriggerInstance.NextRunTime = lastTriggerInstance.NextRunTime.Add(trigger.CreateTimeSpan());
                }
                else
                {
                    thisTriggerInstance.NextRunTime = DateTime.Now.Add(trigger.CreateTimeSpan());
                }
                thisTriggerInstance.WorkflowDesignID = wfDesign.WorkflowGuid;
                thisTriggerInstance.TriggerComponentID = trigger.ComponentID;
                thisTriggerInstance.Interval = trigger.Interval;
                thisTriggerInstance.TimeSpanUnit = trigger.TimeSpanUnit;
                thisTriggerInstance.TriggerType = trigger.TriggerType;
                thisTriggerInstance.IsActive = trigger.IsActive;
                WorkflowTriggerInstanceDALC.AddTriggerInstnace(thisTriggerInstance);

                foreach(ResultBase triggerOutput in triggerOutputs)
                {
                    WorkflowInstance wfInstance=new WorkflowInstance();
                    wfInstance.AuthorEmailAddress = wfDesign.FailureNotifyEmailAddress;
                    wfInstance.HasError = false;
                    wfInstance.IsActive = true;
                    wfInstance.WorkflowID = wfDesign.WorkflowGuid;
                    wfInstance.WorkflowInstanceID = Guid.NewGuid();
                    wfInstance.WorkflowTriggerID = thisTriggerInstance.WorkflowTriggerInstanceID;

                    Run run = this.CreateRunFromWorkflowInstance(wfInstance,thisTriggerInstance);
                    Job triggerJob = new Job(run.RunID, trigger);
                    triggerJob.ReceiveTime = DateTime.Now;
                    triggerJob.StartTime = DateTime.Now;
                    triggerJob.SubmissionTime = DateTime.Now;
                    triggerJob.FinishTime = DateTime.Now;
                    JobDALC.SaveJob(triggerJob);
                    triggerOutput.OwnerWorkerInstanceID = triggerJob.JobID;
                    JobOutputDALC.SaveJobOutput(triggerOutput);

                    if (this.OnWorkflowTriggerFired != null)
                    {
                        List<WorkflowComponentBase> wfComponents = new List<WorkflowComponentBase>();
                        foreach (WorkflowComponentBase wfComponent in wfDesign.Components)
                        {
                            if (!wfComponent.GetType().IsSubclassOf(typeof(WorkflowTriggerBase)))
                            {
                                wfComponents.Add(wfComponent);
                            }
                        }
                        List<Connector> wfConnectors = new List<Connector>();
                        foreach (Connector connector in wfDesign.Connectors)
                        {
                            wfConnectors.Add(connector);
                        }
                        this.OnWorkflowTriggerFired(
                            run,
                            thisTriggerInstance,
                            wfComponents, wfConnectors);
                    }
                    
                }
            }
            

            
            return true;
        }

        /// <summary></summary>
        public List<WorkflowComponentBase> GetWorkflowComponents(WorkflowDesign wfDesign)
        {
            List<WorkflowComponentBase> wfComponents=new List<WorkflowComponentBase>();
            foreach(WorkflowComponentBase wfComponent in wfDesign.Components)
            {
                wfComponents.Add(wfComponent);
            }
            return wfComponents;
        }

        /// <summary></summary>
        public List<WorkflowComponentBase> GetWorkflowComponents(XmlDocument xdoc)
        {
            List<WorkflowComponentBase> wfComponents=new List<WorkflowComponentBase>();
            XmlNodeList xNodes = xdoc.SelectNodes("//Component");
            if (xNodes != null && xNodes.Count > 0)
            {
                foreach (XmlNode xNode in xNodes)
                {
                    string typeFullName = xNode.Attributes.GetNamedItem("Type").Value;
                    if (this._WfComponentTypesByName.ContainsKey(typeFullName))
                    {
                        Type componentType = this._WfComponentTypesByName[typeFullName];
                        WorkflowComponentBase wfComponent =
                            (WorkflowComponentBase) Activator.CreateInstance(componentType);
                        wfComponent = wfComponent.Instantiate(xNode);
                        wfComponents.Add(wfComponent);
                    }
                }
            }
            return wfComponents;
        }

        /// <summary></summary>
        public List<Connector> GetConnectors(XmlDocument xdoc)
        {
            List<Connector> connectors=new List<Connector>();
            XmlNodeList xNodes = xdoc.SelectNodes("//Connector");
            foreach(XmlNode xNode in xNodes)
            {
                Connector connector = Connector.Instantiate(xNode);
                connectors.Add(connector);
            }
            return connectors;
        }

        /// <summary></summary>
        public List<GlobalVariable> GetGlovalVariables(XmlDocument xdoc)
        {
            List<GlobalVariable> wfVars = new List<GlobalVariable>();
            XmlNodeList xNodes = xdoc.SelectNodes("//GlobalVariables/Variable");
            if(xNodes !=null && xNodes.Count>0)
            {
                foreach(XmlNode xNode in xNodes)
                {
                    string varName = xNode.Attributes.GetNamedItem("VariableName").Value;
                    SimpleDataType dataType =
                        (SimpleDataType) int.Parse(xNode.Attributes.GetNamedItem("DataType").Value);
                    string varVal = xNode.Attributes.GetNamedItem("VariableValue").Value;
                    wfVars.Add(new GlobalVariable(varName, dataType, varVal));
                }
            }
            return wfVars;
        }

        /// <summary></summary>
        public WorkflowTriggerBase GetWorkflowTrigger(WorkflowDesign wfDesign)
        {
            if (wfDesign.Components != null && wfDesign.Components.Length > 0)
            {
                foreach (WorkflowComponentBase wfComponent in wfDesign.Components)
                {
                    if (wfComponent.GetType().IsSubclassOf(typeof (WorkflowTriggerBase)))
                    {
                        return (WorkflowTriggerBase) wfComponent;
                    }
                }
            }
            return null;
        }

        /// <summary></summary>
        public Run CreateRunFromWorkflowInstance(WorkflowInstance wfInstance, WorkflowTriggerInstance triggerInstance)
        {
            Run run=new Run(wfInstance.WorkflowInstanceID, wfInstance.WorkflowID, triggerInstance);
            RunDALC.SaveRun(run);
            return run;
        }

        /// <summary></summary>
        public Job CreateJob(Guid runID, WorkflowComponentBase wfComponent, List<Guid> dependentJobIDs, bool isFirst, bool isLast)
        {
            Job job= new Job(runID, wfComponent);
            job.IsFirstJob = isFirst;
            job.IsLastJob = isLast;
            job.Dependency = dependentJobIDs;
            JobDALC.SaveJob(job);
            return job;
        }
        #endregion

        #region connection
        /// <summary></summary>
        public bool IsComponentFirstInWorkflow(WorkflowComponentBase wfComponent, List<WorkflowComponentBase> wfComponents, List<Connector> wfConnectors)
        {
            bool isFirst = true;
            foreach(Connector wfConn in wfConnectors)
            {
                if (wfConn.ToComponentID == wfComponent.ComponentID)
                {
                    isFirst = false;
                    break;
                }
            }
            return isFirst;
        }

        /// <summary></summary>
        public bool IsComponentFirstInWorkflow(WorkflowComponentBase wfComponent, Guid triggerComponentID, List<Connector> wfConnectors)
        {
            bool isFirst = true;
            foreach (Connector wfConn in wfConnectors)
            {
                if (wfConn.ToComponentID == wfComponent.ComponentID && wfConn.FromComponentID != triggerComponentID)
                {
                    isFirst = false;
                    break;
                }
            }
            return isFirst;
        }

        /// <summary></summary>
        public bool IsComponentLastInWorkflow(WorkflowComponentBase wfComponent, List<WorkflowComponentBase> wfComponents, List<Connector> wfConnectors)
        {
            bool isLast = true;
            foreach(Connector wfConn in wfConnectors)
            {
                if(wfConn.FromComponentID==wfComponent.ComponentID)
                {
                    isLast = false;
                    break;
                }
            }
            return isLast;
        }

        /// <summary></summary>
        public List<Guid> GetComponentDependencies(Guid id, List<Connector> wfConnectors, List<WorkflowComponentBase> wfComponents)
        {
            List<Guid> dependencies=new List<Guid>();
            foreach(Connector wfConn in wfConnectors)
            {
                if(wfConn.ToComponentID==id)
                {
                    bool canDependOn = true;
                    foreach(WorkflowComponentBase wfComponent in wfComponents)
                    {
                        if(wfComponent.ComponentID==wfConn.FromComponentID && !wfComponent.Enabled)
                        {
                            canDependOn = false;
                            break;
                        }
                    }
                    if (canDependOn)
                        dependencies.Add(wfConn.FromComponentID);
                }
            }
            return dependencies;
        }
        #endregion
    }
}
